package com.gotobject.junit.internal;

import com.gotobject.junit.internal.runners.model.Measure;
import com.gotobject.junit.internal.runners.model.PerformanceKey;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

import org.junit.runner.Description;

/**
 *
 * @author Huascar A. Sanchez (huascar.sanchez@gmail.com)
 */
public class Registry {
    private final Map<Description, ExecutionRecord> archive;
    private Registry(){
        archive   = newHashMap();
    }

    private static <K, V> Map<K, V> newHashMap(){
        return new ConcurrentHashMap<K, V>();
    }

    /**
     * returns a lazy loaded singleton.
     * @return a {@link Registry} object.
     */
    public static Registry get(){
		return Installer.registry;
	}

    public void register(Description key, ExecutionRecord value){
        archive.put(key, value);
    }

    public ExecutionRecord findRecord(Description forKey){
        return archive.get(forKey);
    }

    public void clear(){
        archive.clear();        
	}

    /**
     * a class which guarantees that the {@link Registry} gets
     * lazy loaded. not synch will be needed using this approach.
     */
    private static class Installer {
        private static Registry registry = new Registry();
        private Installer(){}
    }

    /**
     * @author Huascar A. Sanchez (huascar.sanchez@gmail.com)
     */
    public static class ExecutionRecord {
        private final PerformanceKey    performanceKey;
        private final Measure           measure;
        private final AtomicBoolean     isExecuted;

        public ExecutionRecord(PerformanceKey performanceKey, Measure measure){
            this.performanceKey = performanceKey;
            this.measure        = measure;
            isExecuted          = new AtomicBoolean(true);
        }

        public ExecutionRecord(PerformanceKey performanceKey){
            this(performanceKey, new Measure(0L, 0L, 0L));
        }

        public ExecutionRecord(){
            this(PerformanceKey.UNKNOWN, new Measure(0L, 0L, 0L));
        }

        @Override
        public boolean equals(Object obj) {
            return obj instanceof ExecutionRecord
                    && ExecutionRecord.class.cast(obj).getPerformanceKey().equals(getPerformanceKey())
                    && ExecutionRecord.class.cast(obj).getMeasure().equals(getMeasure());
        }

        @Override
        public int hashCode() {
            return 37 * performanceKey.hashCode() * measure.hashCode();
        }

        public PerformanceKey getPerformanceKey() {
            return performanceKey;
        }

        public Measure getMeasure() {
            return measure;
        }

        public ExecutionRecord executed(){
            isExecuted.getAndSet(true);
            return this;
        }

        public ExecutionRecord skipped(){
            isExecuted.getAndSet(false);
            return this;
        }

        public boolean isExecuted(){
            return isExecuted.get();
        }
    }
}
